# ResultSetIterator.java

    package org.codefilarete.stalactite.sql.result;

    import javax.annotation.Nullable;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.List;
    import java.util.NoSuchElementException;
    import java.util.function.Function;

    import org.codefilarete.tool.function.ThrowingConverter;
    import org.codefilarete.tool.collection.Iterables;
    import org.codefilarete.tool.collection.ReadOnlyIterator;
    import org.codefilarete.tool.exception.Exceptions;

    /**
     * Turns a {@link ResultSet} into an {@link java.util.Iterator} with row conversion support.
     * You can either give the {@link ResultSet} at construct time or set it through {@link #setResultSet(ResultSet)} to reuse
     * this instance.
     * <br/>
     * Then you can iterate over your {@link ResultSetIterator} within a loop such as this one :
     * <pre>
     *     ResultSetIterator<String> resultSetIterator = new ResultSetIterator<String>(resultSet) {
     *         &#64;Override
     *         public String convert(ResultSet rs) throws SQLException {
     *             return rs.getString("name");
     *         }
     *     };
     *     while (resultSetIterator.hasNext()) {
     *         String next = resultSetIterator.next();
     *     }
     * </pre>
     * <strong>Due to the nature of {@link ResultSet} we can't know if a next row exists without reading it, hence {@link #hasNext()} must be called
     * before each access to {@link #next()}, else you'll get {@link NoSuchElementException} on {@link #next()} call.
     * </strong>
     *
     * @author Guillaume Mary
     * @see RowIterator
     * @see #convert()
     * @see #convert(ResultSet)
     * @see #setResultSet(ResultSet)
     */
    public abstract class ResultSetIterator<T> extends ReadOnlyIterator<T> implements ThrowingConverter<ResultSet, T, SQLException> {

    	/**
    	 * The read {@link ResultSet}.
    	 * Can be null if one gave null at construction time, then one can use {@link #setResultSet(ResultSet)} to be non null before iteration
    	 */
    	@Nullable
    	private ResultSet resultSet;

    	/**
    	 * Mimics {@link ResultSet#getRow()} method which is not supported by all databases
    	 * @see #reset()
    	 */
    	private int rowNumber = 1;

    	/**
    	 * Flag that indicates if the {@link ResultSet#next()} method of the underlying instance was called.
    	 * Used for hasNext() since {@link ResultSet} has no sure way of saying if its iteration is started or not
    	 * @see #reset()
    	 */
    	private boolean nextCalled = false;

    	private boolean hasNext = false;

    	/**
    	 * Default constructor
    	 *
    	 * @see #setResultSet(ResultSet)
    	 */
    	public ResultSetIterator() {
    	}

    	/**
    	 * Constructor with a given {@link ResultSet}
    	 *
    	 * @param resultSet a ResultSet to be wrapped in an Iterator.
    	 */
    	public ResultSetIterator(ResultSet resultSet) {
    		this();
    		this.resultSet = resultSet;
    	}

    	/**
    	 * Change the given {@link ResultSet} at construction time. Aimed at reusing this iterator.
    	 *
    	 * @param resultSet
    	 */
    	public void setResultSet(ResultSet resultSet) {
    		this.resultSet = resultSet;
    		reset();
    	}

    	public ResultSet getResultSet() {
    		return resultSet;
    	}

    	public int getRowNumber() {
    		return rowNumber;
    	}

    	/**
    	 * Called when {@link ResultSet} is set. Doesn't rewind the {@link ResultSet} to the beginning.
    	 */
    	private void reset() {
    		this.nextCalled = false;
    		this.hasNext = false;
    		this.rowNumber = 1;
    	}

    	/**
    	 * Returns true if there are more rows in the ResultSet.
    	 * @return boolean true if there are more rows
    	 * @throws RuntimeException if an SQLException occurs.
    	 */
    	@Override
    	public boolean hasNext() {
    		try {
    			// Warn: operator order is very important to prevent next() to be called unnecessarily
    			return (hasNext = !nextCalled && resultSet.next());
    		} catch (SQLException e) {
    			throw Exceptions.asRuntimeException(e);
    		} finally {
    			nextCalled = true;
    		}
    	}

    	@Override
    	public T next() {
    		if (!hasNext) {
    			// this is necessary to be compliant with Iterator#next(..) contract
    			throw new NoSuchElementException();
    		}
    		try {
    			this.rowNumber++;
    			return convert(resultSet);
    		} catch (SQLException e) {
    			throw new RuntimeException("Error while reading ResultSet on row " + --rowNumber, e);
    		} finally {
    			nextCalled = false;
    			hasNext = false;
    		}
    	}

    	/**
    	 * Collects all rows of the {@link ResultSet} set by {@link #setResultSet(ResultSet)} to a {@link List}.
    	 * This basic implementation implies that the size of the list equals the {@link ResultSet} row count.
    	 *
    	 * @return a non null {@link List} containing all converted rows of the {@link ResultSet}
    	 */
    	public List<T> convert() {
    		return Iterables.collectToList(() -> this, Function.identity());
    	}

    	/**
    	 * Converts current row of the ResultSet
    	 *
    	 * @param resultSet the ResultSet given at constructor or {@link #setResultSet}
    	 * @return the result of the conversion
    	 */
    	@Override	// only for Javadoc
    	public abstract T convert(ResultSet resultSet) throws SQLException;

    }